#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# @file: entry/website/models.py
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Contributors:
#   Dawei Yang   <davy.pristina@gmail.com>
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# This file is released under BSD 2-clause license.

import ldap
import hashlib
import base64
from .ldapconfig import *

from ldap.filter import filter_format
from ldap.modlist import addModlist, modifyModlist
from passlib.hash import ldap_salted_sha1, ldap_salted_md5, ldap_md5, ldap_sha1


class User(object):

    def set_password(self, password):
        self.password = password


class LdapUser(object):
    conn = ldap.initialize(LDAP_URL)
    conn.simple_bind_s(ADMIN_DN, LDAP_ADMIN_KEY)
    conn.set_option(ldap.OPT_NETWORK_TIMEOUT, 10.0)

    def __init__(self, attr_dict):
        for key in attr_dict:
            setattr(self, key, attr_dict[key])

    def __getattr__(self, attr):
        if attr in ('employeeNumber', 'givenName'):
            return ['']
        else:
            raise AttributeError

    def check_password_0(self, password):
        pwd = self.userPassword[0]
        pos = pwd.find('}')
        # method = pwd[1: pos]
        encrypt = pwd[pos + 1:]

        dvalue = base64.b64decode(encrypt)
        salt = dvalue[20:]

        newhash = hashlib.sha1(password.encode() + salt).digest()
        calc = base64.b64encode(newhash + salt)
        return calc == encrypt

    def check_password(self, password):
        pwd = self.userPassword[0]
        pos = pwd.find('}')
        method = pwd[1: pos]
        method_module = {
            'MD5': ldap_md5,
            'SMD5': ldap_salted_md5,
            'SHA': ldap_sha1,
            'SSHA': ldap_salted_sha1
        }
        if pos == -1:
            mod = ldap_plaintext
        else:
            mod = method_module[method]

        return mod.verify(password, pwd)

    def is_authenticated(self):
        return True

    def is_active(self):
        return self.is_authenticated()

    def is_anonymous(self):
        return not self.is_authenticated()

    def get_id(self):
        return unicode(self.uid[0])

    @staticmethod
    def query_by_uid(uid):
        return LdapUser.query(filter_format('uid=%s', (uid,)))

    @staticmethod
    def query_by_mail(mail):
        return LdapUser.query(filter_format('mail=%s', (mail,)))

    @staticmethod
    def query_by_student_id(sid):
        return LdapUser.query(filter_format('employeeNumber=%s', (sid,)))

    @staticmethod
    def query(filt):
        results = LdapUser.conn.search_s(BASE_DN, ldap.SCOPE_SUBTREE, filt)
        if len(results) == 0:
            return None
        else:
            return LdapUser(results[0][1])

    @staticmethod
    def modify(ldapUser, **kwargs):
        old = dict()
        new = dict()
        for name, canonical in [
            ('email', 'mail'),
            ('surname', 'sn'),
            # ('common_name', 'cn'),
            ('given_name', 'givenName'),
            ('student_id', 'employeeNumber')
        ]:
            # if kwargs[name] and kwargs[name] != ldapUser.__getattr__(canonical)[0]:
            old[canonical] = ldapUser.__getattr__(canonical)[0]
            new[canonical] = kwargs[name].encode('utf-8')

        if kwargs['password']:
            old['userPassword'] = ldapUser.userPassword
            new['userPassword'] = LdapUser.make_password(kwargs['password'])

        ldif = modifyModlist(old, new)
        LdapUser.conn.modify_s(filter_format('uid=%s,' + BASE_DN, (ldapUser.uid[0],)), ldif)

    @staticmethod
    def sign_up(user):
        attrs = dict()
        attrs['objectClass'] = ['inetOrgPerson', 'organizationalPerson', 'person']
        attrs['uid'] = user.name.encode('utf-8')
        attrs['userPassword'] = user.password.encode('utf-8')
        attrs['mail'] = user.email.encode('utf-8')
        attrs['sn'] = user.surname.encode('utf-8')
        attrs['givenName'] = user.given_name.encode('utf-8')
        # attrs['employeeNumber'] = user.student_id.encode('utf-8')
        attrs['cn'] = attrs['sn'] + ' ' + attrs['givenName']

        ldif = addModlist(attrs)
        LdapUser.conn.add_s(filter_format('uid=%s,' + BASE_DN, (attrs['uid'],)), ldif)

    @staticmethod
    def make_password(cleartext):
        return ldap_salted_sha1.encrypt(cleartext, salt_size=4)
